package fate.tlg;


import java.io.*;

import static fate.BinaryUtils.*;

public class TLGDecoder {
    private byte[] buffer = new byte[256];
    private TLGImage image;

    public TLGDecoder(InputStream input) throws IOException {
        this.image = decode(input);
    }

    public TLGDecoder(byte [] bytes) throws IOException {
        this(new ByteArrayInputStream(bytes));
    }
    
    public TLGDecoder(String path) throws IOException {
        InputStream input = new FileInputStream(path);
        try {
            this.image = decode(input);
        } finally {
            input.close();
        }
    }

    private TLGImage decode(InputStream input) throws IOException {
        skipStr(input, "TLG5.0\0raw\u001a", buffer);
        int colors = readubyte(input);
        if (colors != 3 && colors != 4) {
            throw new RuntimeException("colors must be 3 or 4");
        }
        int width = readuint(input, buffer);
        int height = readuint(input, buffer);
        int blockheight = readuint(input, buffer);
        int blockcount = (height-1)/blockheight + 1;
        input.skip(blockcount*4);

        byte[] text = new byte[4096];
        byte[] out = new byte[width*height*colors];
        byte[][] outbuf = new byte[colors][(blockheight * width + 10 + 3) & ~3];
        int xr = 0;

        int prevline = -1;
        for (int y_blk = 0; y_blk < height; y_blk += blockheight) {
            for (int c = 0; c < colors; c++) {
                int mark = readubyte(input);
                int size = readuint(input, buffer);
                if (mark == 0) {
                    xr = TVPTLG5DecompressSlide(outbuf[c], input, size, text, xr);
                }
                else {
                    input.read(outbuf[c], 0, size);
                }
            }

            int y_lim = y_blk + blockheight;
            if (y_lim > height) {
                y_lim = height;
            }

            int[] outbufi = new int[colors];

            for (int y = y_blk; y < y_lim; y++) {
                int currenti = y * width * colors; // out
			    int current_orgi = currenti;

                if (prevline != -1) {
                    if (colors == 3) {
                        TVPTLG5ComposeColors3(out, outbuf, currenti, prevline, outbufi, width);
                        outbufi[0] += width;
                        outbufi[1] += width;
                        outbufi[2] += width;
                    }
                    else if (colors == 4) {
                        TVPTLG5ComposeColors4(out, outbuf, currenti, prevline, outbufi, width);
                        outbufi[0] += width;
                        outbufi[1] += width;
                        outbufi[2] += width;
                        outbufi[3] += width;
                    }
                    else {
                        throw new RuntimeException("Colors must be 3 or 4");
                    }
                }
                else {
                    byte pr = 0, pg = 0, pb = 0, pa = 0;
                    if (colors == 3) {
                        for (int x = 0; x < width; x++) {
                            byte b = outbuf[0][outbufi[0] + x];
                            byte g = outbuf[1][outbufi[1] + x];
                            byte r = outbuf[2][outbufi[2] + x];

                            b += g;
                            r += g;
                            out[currenti++] = pb += b;
                            out[currenti++] = pg += g;
                            out[currenti++] = pr += r;
                        }
                        outbufi[0] += width;
                        outbufi[1] += width;
                        outbufi[2] += width;
                    }
                    else if (colors == 4) {
                        for (int x = 0; x < width; x++) {
                            byte b = outbuf[0][outbufi[0] + x];
                            byte g = outbuf[1][outbufi[1] + x];
                            byte r = outbuf[2][outbufi[2] + x];
                            byte a = outbuf[3][outbufi[3] + x];

                            b += g;
                            r += g;
                            out[currenti++] = pb += b;
                            out[currenti++] = pg += g;
                            out[currenti++] = pr += r;
                            out[currenti++] = pa += a;
                        }
                        outbufi[0] += width;
                        outbufi[1] += width;
                        outbufi[2] += width;
                        outbufi[3] += width;
                    }
                    else {
                        throw new RuntimeException("Colors must be 3 or 4");
                    }
                }

                prevline = current_orgi;
            }

//            if (colors == 4) {
//                int bi = 0;
//                int pixels = width * height;
//                for (int i = 0; i < pixels; ++i) {
//                    out[0] = (byte)((out[0]&0xff) * (out[3]&0xff) / 255 + (255 - (out[3]&0xff)));
//                    out[1] = (byte)((out[1]&0xff) * (out[3]&0xff) / 255 + (255 - (out[3]&0xff)));
//                    out[2] = (byte)((out[2]&0xff) * (out[3]&0xff) / 255 + (255 - (out[3]&0xff)));
//                    bi += 4;
//                }
//            }
        }
        return new TLGImage(width, height, colors, out);
    }

    private int TVPTLG5DecompressSlide(byte[] out, InputStream input, int size, byte[] text, int r) throws IOException {
        int flags = 0;
        int outi = 0;
        int n = 0;

        byte [] buffer = new byte[size];
        int nread = input.read(buffer);
        if (nread != size) {
            throw new EOFException();
        }

        while (n < size) {
            if ( ((flags >>= 1) & 0x100) == 0) {
                flags = (buffer[n++]&0xff) | 0xff00;
            }

            if ( (flags & 1) != 0) {
                int mpos = (buffer[n+0] & 0xff) | ((buffer[n+1] & 0xf) << 8);
                int mlen = (buffer[n+1] & 0xf0) >> 4;
                n += 2;
                mlen += 3;
                if (mlen == 18) {
                    mlen += buffer[n++] & 0xff;
                }

                while (mlen-- > 0) {
                    out[outi++] = text[r++] = text[mpos++];
                    mpos &= (4096 - 1);
                    r &= (4096 - 1);
                }
            } else {
                byte c = buffer[n++];
                out[outi++] = c;
                text[r++] = c;
                r &= (4096 - 1);
            }
        }

        return r;

    }

    private void TVPTLG5ComposeColors3(byte[] out, byte[][] outbuf, int outi, int upperi, int[] bufi, int width) {
        byte[] pc = new byte[3];
        byte[] c = new byte[3];

        pc[0] = pc[1] = pc[2] = 0;
        for (int x = 0; x < width; x++) {
            c[0] = outbuf[0][bufi[0]+x];
            c[1] = outbuf[1][bufi[1]+x];
            c[2] = outbuf[2][bufi[2]+x];
            c[0] += c[1]; c[2] += c[1];
            out[outi+0] = (byte)((pc[0] += c[0]) + out[upperi+0]); // why i have to cast?
            out[outi+1] = (byte)((pc[1] += c[1]) + out[upperi+1]);
            out[outi+2] = (byte)((pc[2] += c[2]) + out[upperi+2]);
            outi += 3;
            upperi += 3;
        }
    }

    private void TVPTLG5ComposeColors4(byte[] out, byte[][] outbuf, int outi, int upperi, int[] bufi, int width) {
        byte[] pc = new byte[4];
        byte[] c = new byte[4];
        pc[0] = pc[1] = pc[2] = pc[3] = 0;
        for (int x = 0; x < width; x++) {
            c[0] = outbuf[0][bufi[0]+x];
            c[1] = outbuf[1][bufi[1]+x];
            c[2] = outbuf[2][bufi[2]+x];
            c[3] = outbuf[3][bufi[3]+x];
            c[0] += c[1]; c[2] += c[1];
            out[outi+0] = (byte)((pc[0] += c[0]) + out[upperi+0]); // why i have to cast?
            out[outi+1] = (byte)((pc[1] += c[1]) + out[upperi+1]);
            out[outi+2] = (byte)((pc[2] += c[2]) + out[upperi+2]);
            out[outi+3] = (byte)((pc[3] += c[3]) + out[upperi+3]);
            outi += 4;
            upperi += 4;
        }
    }

    public TLGImage getImage() {
        return image;
    }

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        TLGImage image = new TLGDecoder("D:\\Projects\\Special\\libfate\\libfate-py\\allclear_title2.tlg").getImage();
        byte[] buffer = image.getImageBuffer();
//        for (int x = 0; x < image.getHeight(); x++) {
//            System.out.print("[");
//            for (int y = 0; y < image.getWidth(); y++) {
//                int z = (x * image.getWidth() + y) * image.getColors();
//                String row = (buffer[z + 0]&0xff) + ", " + (buffer[z + 1]&0xff) + ", " + (buffer[z + 2]&0xff);
//                if (image.getColors() == 3) {
//                    System.out.print("(" + row + "), ");
//                } else {
//                    System.out.print("(" + row + ", " + (buffer[z + 3]&0xff) + "), ");
//                }
//
//            }
//            System.out.println("]");
//        }
//        byte z = 0;
//        for (byte b: buffer) z ^= b;
//        System.out.println(z&0xff);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}
